2-2. 자격증명 관리 계열

관통 질문: 이 비밀(패스워드·계정·키)이 소스 코드 안에 있는가? 4개 항목 모두 “비밀을 코드 안에 둔다 — 바꿀 수도, 숨길 수도 없다”는 한 문장으로 요약된다. 컴파일된 바이너리에도 문자열 상수는 그대로 남는다. strings 명령 하나면 목록이 나온다.


5. 하드코드된 패스워드 CWE-259

신뢰 경계: 소스 코드 상수 → 그대로 → DB 접속 인증 ⚠️ 소스에 박힌 비밀번호는 바이너리에도 남는다. 패스워드는 코드가 아니라 설정·저장소에서 암호화된 상태로 로드하라.

공격 시나리오: strings 명령 한 번으로 DB 계정·관리자 패스워드가 드러나고, 배포된 모든 설치본이 동일한 비밀번호를 공유한다. 변경하려면 재컴파일·재배포가 필요하다.

❌ Bad

SQLConnect(hdbc, (SQLCHAR*)server, strlen(server),
    user, strlen(user), "asdf", 4);          /* 패스워드가 하드코딩 */

✅ Good

int dbaccess(char *server, char *user, char *passwd)     /* 외부에서 전달받는다 */
{
    SQLConnect(hdbc, (SQLCHAR*)server, strlen(server),
        user, strlen(user), passwd, strlen(passwd));
}
char *adminPassword = loadPassword(ADMIN);                /* 저장소에서 로드 */

치환 규칙: 소스 상수 패스워드 삭제 → 함수 인자로 전달받거나 설정파일/저장소에서 암호화된 값을 로드.


6. 하드코드된 사용자 계정 CWE-255

신뢰 경계: 소스 코드 상수("root", "DBAdmin") → 그대로 → 인증 계정 ⚠️ 하드코드된 계정은 “고칠 수 없는 백도어”다. 바꾸려면 재컴파일과 전체 정지가 필요하다.

공격 시나리오: 프로그램만 실행할 수 있으면 누구나 인증 없이 DB에 접근할 수 있고, 바이너리 문자열 추출로 계정과 패스워드가 노출된다.

❌ Bad

SQLConnect(hdbc, (SQLCHAR*)server, strlen(server),
    "root", 4, passwd, strlen(passwd));

✅ Good

char * dbUserId, * dbUserPassword;
loadDbUserInfo(&dbUserId, &dbUserPassword);              /* 검증 과정을 거쳐 획득 */
SQLConnect(hdbc, (SQLCHAR*)"173.234.56.78", SQL_NTS,
    (SQLCHAR*)dbUserId, strlen(dbUserId),
    dbUserPassword, strlen(dbUserPassword));

치환 규칙: 계정명 상수 삭제 → 외부(설정·자격증명 저장소)에서 로딩·검증하는 별도 루틴으로 획득.


7. 하드코드된 암호화 키 CWE-321

신뢰 경계: 소스 코드 상수 배열(AES 키) → 그대로 → 암호화 연산 키 인자 ⚠️ 키를 코드에 박으면 키 교체가 불가능해진다. 키는 코드 밖(키 저장소·설정파일)에서 주입받아야 한다.

공격 시나리오: 키가 바이너리에 박혀 있어 리버싱으로 추출되면 그 키로 암호화된 모든 데이터가 한 번에 복호화되고, 키 교체(rotation)도 불가능하다.

❌ Bad

static const unsigned char KEY[16] = { 0x13,0x2a,0x7f, /* ... */ };
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, KEY, iv);

✅ Good

unsigned char key[16];
if (loadKeyFromKeystore("db-enc-key", key, sizeof(key)) != 0) {
    denyAccess(); return -1;
}
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
OPENSSL_cleanse(key, sizeof(key));          /* 사용 후 메모리에서 지운다 */

치환 규칙: 키 상수 배열 삭제 → 키 저장소·HSM·권한 제한 키 파일에서 런타임 주입, 사용 후 메모리 클리어.


8. 주석문안에 포함된 패스워드 등 시스템 주요정보 CWE-615

신뢰 경계: 개발 편의로 남긴 주석 → 제거 없이 → 배포 소스/저장소 ⚠️ 주석은 컴파일되지 않을 뿐 사라지지 않는다. 배포 전 “주석 청소”를 릴리스 체크리스트에 넣어라.

공격 시나리오: 소스코드나 배포물에 한 번만 접근하면 인증 절차 없이 관리자 패스워드와 시스템 내부 구조를 획득할 수 있다.

❌ Bad

/* default password is "abracadabra" <- 주석에 패스워드 */
int verifyAuth(char *ipasswd, char *orgpasswd) { ... }

✅ Good

/* 주석에 패스워드를 기입하지 않는다 */
int verifyAuth(char *ipasswd, char *orgpasswd) { ... }

치환 규칙: 배포 전 grep/정적분석으로 password/key/id 키워드 주석 전수 검사 후 제거.


중간 점검 — 인가와 자격증명

  1. 로그인에 성공한 사용자가 GET /report?user_id=1234 로 리포트를 조회한다. 서버는 세션이 유효한지만 확인하고 user_id 로 조회한다. 무엇이 문제일까?
  2. DB 접속 비밀번호를 소스에서 빼서 환경변수로 옮겼다. 이제 안전한가?
  3. “이미 배포된 바이너리에는 비밀번호가 안 보인다”는 주장은 맞을까?

이 계열 미니 체크리스트

  • 소스 코드에 패스워드·계정명·암호화 키가 상수로 박혀 있는가
  • 자격증명이 환경변수에만 의존하고 있지는 않은가(파일·프로세스 노출 경로 점검)
  • 주석에 password/key/id/내부 URL 이 남아 있는가 (배포 전 grep 검사 여부)
  • 암호화 키를 사용 후 메모리에서 지우는가(OPENSSL_cleanse 등)

관련 노트